﻿using System;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.ComponentModel;
using Microsoft.Windows.UserControls.Core;
using Microsoft.Windows.UserControls.Core.Providers;

namespace Microsoft.Windows.UserControls
{
    public class Wizard : ItemsControl
    {
        #region  Dependency Properties
        
        public readonly static DependencyProperty CurrentPageProperty = DependencyProperty.Register("CurrentPage", typeof(WizardPage), typeof(Wizard), new UIPropertyMetadata(null, OnCurrentPageChanged));
        public readonly static DependencyProperty DirectionProperty = DependencyProperty.Register("Direction", typeof(WizardDirectionType), typeof(Wizard), new UIPropertyMetadata(null));
        
        public readonly static DependencyProperty CommandProviderProperty = DependencyProperty.Register("CommandProvider", typeof(WizardCommandProvider), typeof(Wizard), new PropertyMetadata(OnCommandProviderPropertyChanged));
        public readonly static DependencyPropertyKey CanHelpExecuteCommandPropertyKey = DependencyProperty.RegisterReadOnly("CanHelpExecuteCommand", typeof(Boolean), typeof(Wizard), new PropertyMetadata(CanExecuteCommandPropertyChanged));
        public readonly static DependencyProperty CanHelpExecuteCommandProperty = Wizard.CanHelpExecuteCommandPropertyKey.DependencyProperty;
        public readonly static DependencyPropertyKey CanPreviousExecuteCommandPropertyKey = DependencyProperty.RegisterReadOnly("CanPreviousExecuteCommand", typeof(Boolean), typeof(Wizard), new PropertyMetadata(CanExecuteCommandPropertyChanged));
        public readonly static DependencyProperty CanPreviousExecuteCommandProperty = Wizard.CanPreviousExecuteCommandPropertyKey.DependencyProperty;
        public readonly static DependencyPropertyKey CanNextExecuteCommandPropertyKey = DependencyProperty.RegisterReadOnly("CanNextExecuteCommand", typeof(Boolean), typeof(Wizard), new PropertyMetadata(CanExecuteCommandPropertyChanged));
        public readonly static DependencyProperty CanNextExecuteCommandProperty = Wizard.CanNextExecuteCommandPropertyKey.DependencyProperty;
        public readonly static DependencyPropertyKey CanCancelExecuteCommandPropertyKey = DependencyProperty.RegisterReadOnly("CanCancelExecuteCommand", typeof(Boolean), typeof(Wizard), new PropertyMetadata(CanExecuteCommandPropertyChanged));
        public readonly static DependencyProperty CanCancelExecuteCommandProperty = Wizard.CanCancelExecuteCommandPropertyKey.DependencyProperty;
        public readonly static DependencyProperty CancelExecuteCommandCloseWindowProperty = DependencyProperty.Register("CancelExecuteCommandCloseWindow", typeof(Boolean), typeof(Wizard), new UIPropertyMetadata(true));
        public readonly static DependencyPropertyKey CanFinishExecuteCommandPropertyKey = DependencyProperty.RegisterReadOnly("CanFinishExecuteCommand", typeof(Boolean), typeof(Wizard), new PropertyMetadata(CanExecuteCommandPropertyChanged));
        public readonly static DependencyProperty CanFinishExecuteCommandProperty = Wizard.CanFinishExecuteCommandPropertyKey.DependencyProperty;
        public readonly static DependencyProperty FinishExecuteCommandCloseWindowProperty = DependencyProperty.Register("FinishExecuteCommandCloseWindow", typeof(Boolean), typeof(Wizard), new UIPropertyMetadata(true));
        
        #endregion
        
        #region Properties
        
        public WizardPage CurrentPage
        {
            get
            {
                return (WizardPage)base.GetValue(CurrentPageProperty);
            }
            set
            {
                base.SetValue(CurrentPageProperty, value);
            }
        }
        
        public Int32 CurrentPageIndex
        {
            get
            {
                if (CurrentPage == null)
                    return -1;
                return Items.IndexOf(CurrentPage);
            }
        }
        
        internal WizardDirectionType Direction
        {
            get
            {
                return (WizardDirectionType)base.GetValue(DirectionProperty);
            }
            set
            {
                base.SetValue(DirectionProperty, value);
            }
        }
        
        public WizardCommandProvider CommandProvider
        {
            get
            {
                return (WizardCommandProvider)base.GetValue(CommandProviderProperty);
            }
            set
            {
                base.SetValue(CommandProviderProperty, value);
            }
        }
        
        public Boolean CanHelpExecuteCommand
        {
            get
            {
                return (Boolean)base.GetValue(CanHelpExecuteCommandProperty);
            }
            private set
            {
                base.SetValue(CanHelpExecuteCommandPropertyKey, value);
            }
        }
        
        public Boolean CanPreviousExecuteCommand
        {
            get
            {
                return (Boolean)base.GetValue(CanPreviousExecuteCommandProperty);
            }
            private set
            {
                base.SetValue(CanPreviousExecuteCommandPropertyKey, value);
            }
        }
        
        public Boolean CanNextExecuteCommand
        {
            get
            {
                return (Boolean)base.GetValue(CanNextExecuteCommandProperty);
            }
            private set
            {
                base.SetValue(CanNextExecuteCommandPropertyKey, value);
            }
        }
        
        public Boolean CanCancelExecuteCommand
        {
            get
            {
                return (Boolean)base.GetValue(CanCancelExecuteCommandProperty);
            }
            private set
            {
                base.SetValue(CanCancelExecuteCommandPropertyKey, value);
            }
        }
        
        public Boolean CancelExecuteCommandCloseWindow
        {
            get
            {
                return (Boolean)base.GetValue(CancelExecuteCommandCloseWindowProperty);
            }
            private set
            {
                base.SetValue(CancelExecuteCommandCloseWindowProperty, value);
            }
        }
        
        public Boolean CanFinishExecuteCommand
        {
            get
            {
                return (Boolean)base.GetValue(CanFinishExecuteCommandProperty);
            }
            private set
            {
                base.SetValue(CanFinishExecuteCommandPropertyKey, value);
            }
        }
        
        public Boolean FinishExecuteCommandCloseWindow
        {
            get
            {
                return (Boolean)base.GetValue(FinishExecuteCommandCloseWindowProperty);
            }
            private set
            {
                base.SetValue(FinishExecuteCommandCloseWindowProperty, value);
            }
        }
        
        Boolean onItemsChangedExecuted = false;
        Boolean currentPageChangingEventCanceled = false;
        
        #endregion
        
        #region ctor
        
        static Wizard()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Wizard), new FrameworkPropertyMetadata(typeof(Wizard)));
        }
        
        public Wizard()
        {
        }
        
        #endregion
        
        #region Static Methods
        
        static void OnCurrentPageChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var control = sender as Wizard;
            if (control == null)
                return;
            
            if (control.currentPageChangingEventCanceled)
                return;
            
            var oldPage = args.OldValue as WizardPage;
            var newPage = args.NewValue as WizardPage;
            var oldPageIndex = oldPage != null ? control.Items.IndexOf(oldPage) : -1;
            var newPageIndex = newPage != null ? control.Items.IndexOf(newPage) : -1;
            
            if (newPage != null)
            {
                control.UpdateDirection(oldPageIndex, newPageIndex);
                var currentPageChangingEventArgs = new CurrentPageChangingEventArgs(oldPage, newPage, control.Direction);
                control.OnCurrentPageChanging(currentPageChangingEventArgs);
                if (currentPageChangingEventArgs.Cancel)
                {
                    control.currentPageChangingEventCanceled = true;
                    control.CurrentPage = oldPage;
                    control.currentPageChangingEventCanceled = false;
                    return;
                }
                control.OnCurrentPageChanged(new CurrentPageChangedEventArgs(oldPage, newPage, control.Direction));
                control.SetCanExecuteAllCommands();
            }
        }
        
        static void OnCommandProviderPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var provider = args.NewValue as WizardCommandProvider;
            if (provider == null)
                return;
            
            provider.Wizard = sender as Wizard;
            provider.RegisterCommandBindings();
        }
        
        static void CanExecuteCommandPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            if (((Wizard)sender) != null)
                CommandManager.InvalidateRequerySuggested();
        }
        
        internal static void RegisterCommands()
        {
            WizardCommandProvider.Register(Core.Commands.WizardCommands.Help, HelpCommandExecute, CanHelpCommandExecute);
            WizardCommandProvider.Register(Core.Commands.WizardCommands.Previous, PreviousCommandExecute, CanPreviousCommandExecute);
            WizardCommandProvider.Register(Core.Commands.WizardCommands.Next, NextCommandExecute, CanNextCommandExecute);
            WizardCommandProvider.Register(Core.Commands.WizardCommands.Cancel, CancelCommandExecute, CanCancelCommandExecute);
            WizardCommandProvider.Register(Core.Commands.WizardCommands.Finish, FinishCommandExecute, CanFinishCommandExecute);
        }
        
        static void HelpCommandExecute(object sender, ExecutedRoutedEventArgs args)
        {
            var control = sender as Wizard;
            if (control != null && control.CommandProvider != null)
                control.OnHelpCommandExecute(new WizardButtonCommandEventArgs(control.CurrentPageIndex, WizardButton.ButtonCommandType.Help));
        }
        
        static void CanHelpCommandExecute(object sender, CanExecuteRoutedEventArgs args)
        {
            var control = sender as Wizard;
            if (control != null)
                args.CanExecute = (control.CommandProvider != null ? control.CommandProvider.CanHelpExecute() : false);
        }
        
        static void PreviousCommandExecute(object sender, ExecutedRoutedEventArgs e)
        {
            var control = sender as Wizard;
            if (control != null && control.CommandProvider != null)
                control.OnPreviousCommandExecute(new WizardButtonCommandEventArgs(control.CurrentPageIndex, WizardButton.ButtonCommandType.Previous));
        }
        
        static void CanPreviousCommandExecute(object sender, CanExecuteRoutedEventArgs args)
        {
            var control = sender as Wizard;
            if (control != null)
                args.CanExecute = (control.CommandProvider != null ? control.CommandProvider.CanPreviousExecute() : false);
        }
        
        static void NextCommandExecute(object sender, ExecutedRoutedEventArgs e)
        {
            var control = sender as Wizard;
            if (control != null && control.CommandProvider != null)
                control.OnNextCommandExecute(new WizardButtonCommandEventArgs(control.CurrentPageIndex, WizardButton.ButtonCommandType.Next));
        }
        
        static void CanNextCommandExecute(object sender, CanExecuteRoutedEventArgs args)
        {
            var control = sender as Wizard;
            if (control != null)
                args.CanExecute = (control.CommandProvider != null ? control.CommandProvider.CanNextExecute() : false);
        }
        
        static void CancelCommandExecute(object sender, ExecutedRoutedEventArgs e)
        {
            var control = sender as Wizard;
            if (control != null && control.CommandProvider != null)
                control.OnCancelCommandExecute(new WizardButtonCommandEventArgs(control.CurrentPageIndex, WizardButton.ButtonCommandType.Cancel));
        }
        
        static void CanCancelCommandExecute(object sender, CanExecuteRoutedEventArgs args)
        {
            var control = sender as Wizard;
            if (control != null)
                args.CanExecute = (control.CommandProvider != null ? control.CommandProvider.CanCancelExecute() : false);
        }
        
        static void FinishCommandExecute(object sender, ExecutedRoutedEventArgs e)
        {
            var control = sender as Wizard;
            if (control != null && control.CommandProvider != null)
                control.OnFinishCommandExecute(new WizardButtonCommandEventArgs(control.CurrentPageIndex, WizardButton.ButtonCommandType.Finish));
        }
        
        static void CanFinishCommandExecute(object sender, CanExecuteRoutedEventArgs args)
        {
            var control = sender as Wizard;
            if (control != null)
                args.CanExecute = (control.CommandProvider != null ? control.CommandProvider.CanFinishExecute() : false);
        }
        
        #endregion
        
        #region Overrides
        
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new WizardPage();
        }
        
        protected override Boolean IsItemItsOwnContainerOverride(object item)
        {
            return (item is WizardPage);
        }
        
        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            if (Items.Count > 0 && CurrentPage == null)
                CurrentPage = Items[0] as WizardPage;
            CommandProvider = new WizardCommandProvider(this);
        }
        
        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnItemsChanged(e);
            if (onItemsChangedExecuted)
                return;
            
            var wizardPageWelcome = default(WizardPage);
            var wizardPageCompletion = default(WizardPage);
            var wizardPageNormal = default(WizardPage);
            foreach (var item in Items)
            {
                var wizardPage = item as WizardPage;
                if (wizardPage == null)
                    throw new NotSupportedException("Wizard should only contains WizardPages.");
                
                switch (wizardPage.PageType)
                {
                    case WizardPage.WizardPageType.Normal:
                        wizardPageNormal = wizardPage;
                        break;
                    case WizardPage.WizardPageType.Welcome:
                        if (wizardPageWelcome != null)
                            throw new NotSupportedException("Wizard should contains a WizardPage with PageType \"Welcome\" only once.");
                        wizardPageWelcome = wizardPage;
                        break;
                    case WizardPage.WizardPageType.Completion:
                        if (wizardPageCompletion != null)
                            throw new NotSupportedException("Wizard should contains a WizardPage with PageType \"Completion\" only once.");
                        wizardPageCompletion = wizardPage;
                        break;
                    default:
                        break;
                }
            }
            
            if (wizardPageNormal == null)
                throw new NotSupportedException("Wizard should contains at least one WizardPage with PageType \"Normal\".");
            
            if (wizardPageWelcome == null)
                throw new NotSupportedException("Wizard should contains a WizardPage with PageType \"Welcome\".");
            
            if (wizardPageCompletion == null)
                throw new NotSupportedException("Wizard should contains a WizardPage with PageType \"Completion\".");
            
            if (Items.IndexOf(wizardPageWelcome) != 0)
            {
                onItemsChangedExecuted = true;
                Items.Remove(wizardPageWelcome);
                Items.Insert(0, wizardPageWelcome);
                onItemsChangedExecuted = false;
            }
            
            if (Items.IndexOf(wizardPageCompletion) != (Items.Count - 1))
            {
                onItemsChangedExecuted = true;
                Items.Remove(wizardPageCompletion);
                Items.Add(wizardPageCompletion);
                onItemsChangedExecuted = false;
            }
            
            if (DesignerProperties.GetIsInDesignMode(this) && Items.Count > 0 && CurrentPage == null)
                CurrentPage = (WizardPage)Items[0];
        }
        
        #endregion
        
        #region Methods
        
        void UpdateDirection(Int32 oldPageIndex, Int32 newPageIndex)
        {
            Direction = (newPageIndex >= oldPageIndex ? WizardDirectionType.Forward : WizardDirectionType.Backward);
        }
        
        protected virtual void CloseWindow(Boolean dialogResult)
        {
            var window = Window.GetWindow(this);
            if (window == null)
                return;
            
            if (ComponentDispatcher.IsThreadModal)
                window.DialogResult = dialogResult;
            window.Close();
        }
        
        #endregion
        
        public event EventHandler<WizardButtonCommandEventArgs> Help;
        
        protected internal virtual void OnHelpCommandExecute(WizardButtonCommandEventArgs args)
        {
            if (Help == null)
                return;
            Help(this, args);
        }
        
        public event EventHandler<WizardButtonCommandEventArgs> Previous;
        
        protected internal virtual void OnPreviousCommandExecute(WizardButtonCommandEventArgs args)
        {
            if (args.CommandType != WizardButton.ButtonCommandType.Previous)
                return;
            if (Items.Count <= 0)
                return;
            
            var pageIndex = args.CurrentPageIndex;
            if (Previous != null)
            {
                Previous(this, args);
                if (args.CurrentPageIndex != pageIndex)
                    pageIndex = args.CurrentPageIndex;
                
                if (pageIndex < CurrentPageIndex && pageIndex >= 0)
                {
                    CurrentPage = Items[pageIndex] as WizardPage;
                    return;
                }
                else
                    pageIndex = CurrentPageIndex - 1;
            }
            else
                pageIndex--;
            
            if (pageIndex >= 0)
                CurrentPage = Items[pageIndex] as WizardPage;
        }
        
        public event EventHandler<WizardButtonCommandEventArgs> Next;
        
        protected internal virtual void OnNextCommandExecute(WizardButtonCommandEventArgs args)
        {
            if (args.CommandType != WizardButton.ButtonCommandType.Next)
                return;
            if (Items.Count <= 0)
                return;
            
            var pageIndex = args.CurrentPageIndex;
            if (Next != null)
            {
                Next(this, args);
                
                if (args.CurrentPageIndex != pageIndex)
                    pageIndex = args.CurrentPageIndex;
                
                if (pageIndex > CurrentPageIndex && pageIndex <= Items.Count)
                {
                    CurrentPage = Items[pageIndex] as WizardPage;
                    return;
                }
                else
                    pageIndex = CurrentPageIndex + 1;
            }
            else
                pageIndex++;
            
            if (pageIndex < Items.Count)
                CurrentPage = Items[pageIndex] as WizardPage;
        }
        
        public event EventHandler<WizardButtonCommandEventArgs> Cancel;
        
        protected internal virtual void OnCancelCommandExecute(WizardButtonCommandEventArgs args)
        {
            if (Cancel != null)
                Cancel(this, args);
            
            var wizardCompletingEventArg = new WizardCompletingEventArgs(CurrentPage, WizardCompletionActionType.Cancel);
            OnWizardCompleting(wizardCompletingEventArg);
            if (wizardCompletingEventArg.Cancel)
                return;
            OnWizardCompleted(new WizardCompletedEventArgs(CurrentPage, WizardCompletionActionType.Cancel));
            if (CancelExecuteCommandCloseWindow)
                CloseWindow(false);
        }
        
        public event EventHandler<WizardButtonCommandEventArgs> Finish;
        
        protected internal virtual void OnFinishCommandExecute(WizardButtonCommandEventArgs args)
        {
            if (Finish != null)
                Finish(this, args);
            
            var wizardCompletingEventArg = new WizardCompletingEventArgs(CurrentPage, WizardCompletionActionType.Finish);
            OnWizardCompleting(wizardCompletingEventArg);
            if (wizardCompletingEventArg.Cancel)
                return;
            OnWizardCompleted(new WizardCompletedEventArgs(CurrentPage, WizardCompletionActionType.Finish));
            if (FinishExecuteCommandCloseWindow)
                CloseWindow(true);
        }
        
        public event EventHandler<CurrentPageEventArgs> WizardPageLoaded;
        
        protected internal virtual void OnWizardPageLoaded(WizardPage sender)
        {
            if (WizardPageLoaded != null)
                WizardPageLoaded(this, new CurrentPageEventArgs(sender));
        }
        
        public event EventHandler<CurrentPageChangedEventArgs> CurrentPageChanged;
        
        protected virtual void OnCurrentPageChanged(CurrentPageChangedEventArgs args)
        {
            if (CurrentPageChanged != null)
                CurrentPageChanged(this, args);
        }
        
        public event EventHandler<CurrentPageChangingEventArgs> CurrentPageChanging;
        
        protected virtual void OnCurrentPageChanging(CurrentPageChangingEventArgs args)
        {
            if (CurrentPageChanging != null)
                CurrentPageChanging(this, args);
        }
        
        public event EventHandler<WizardCompletingEventArgs> WizardCompleting;
        
        protected virtual void OnWizardCompleting(WizardCompletingEventArgs args)
        {
            if (WizardCompleting != null)
                WizardCompleting(this, args);
        }
        
        public event EventHandler<WizardCompletedEventArgs> WizardCompleted;
        
        protected virtual void OnWizardCompleted(WizardCompletedEventArgs args)
        {
            if (WizardCompleted != null)
                WizardCompleted(this, args);
        }
        
        internal void SetCanExecuteAllCommands()
        {
            SetCanExecuteHelpCommand();
            SetCanExecutePreviousCommand();
            SetCanExecuteNextCommand();
            SetCanExecuteCancelCommand();
            SetCanExecuteFinishCommand();
        }
        
        internal void SetCanExecuteHelpCommand()
        {
            if (CurrentPage == null)
                return;
            
            if (Items.Count <= 0 || CurrentPageIndex > Items.Count - 1 || CurrentPage.PageType == WizardPage.WizardPageType.Completion)
                CanHelpExecuteCommand = false;
            else 
                CanHelpExecuteCommand = CurrentPage.AllowHelpExecuteCommand;
        }
        
        internal void SetCanExecutePreviousCommand()
        {
            if (CurrentPage == null)
                return;
            
            if (Items.Count <= 0 || CurrentPageIndex > Items.Count - 1 || CurrentPage.PageType != WizardPage.WizardPageType.Normal)
                CanPreviousExecuteCommand = false;
            else if (CurrentPageIndex == 1)
                CanPreviousExecuteCommand = false;
            else
                CanPreviousExecuteCommand = CurrentPage.AllowPreviousExecuteCommand;
        }
        
        internal void SetCanExecuteNextCommand()
        {
            if (CurrentPage == null)
                return;
            
            if (Items.Count <= 0 || CurrentPageIndex > Items.Count - 1 || CurrentPage.PageType == WizardPage.WizardPageType.Completion)
                CanNextExecuteCommand = false;
            else 
                CanNextExecuteCommand = CurrentPage.AllowNextExecuteCommand;
        }
        
        internal void SetCanExecuteCancelCommand()
        {
            if (CurrentPage == null)
                return;
            
            if (Items.Count <= 0 || CurrentPageIndex > Items.Count - 1 || CurrentPage.PageType == WizardPage.WizardPageType.Completion)
                CanCancelExecuteCommand = false;
            else 
                CanCancelExecuteCommand = CurrentPage.AllowCancelExecuteCommand;
        }
        
        internal void SetCanExecuteFinishCommand()
        {
            if (CurrentPage == null)
                return;
            
            if (Items.Count <= 0 || CurrentPageIndex > Items.Count - 1 || CurrentPage.PageType != WizardPage.WizardPageType.Completion)
                CanFinishExecuteCommand = false;
            else 
                CanFinishExecuteCommand = CurrentPage.AllowFinishExecuteCommand;
        }
        
        public enum WizardDirectionType
        {
            Forward,
            Backward
        }
        
        public enum WizardCompletionActionType
        {
            Cancel,
            Finish
        }
    }
}